/*
*  This file is part of mangusta
*
*  mangusta is free software; you can redistribute it and/or modify
*  it under the terms of the GNU General Public License as published by
*  the Free Software Foundation; either version 2 of the License, or
*  (at your option) any later version.
*
*  mangusta is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public License
*  along with mangusta; if not, write to the Free Software
*  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  US
*
*  Author: <antonino.calderone@ericsson.com>, <acaldmail@gmail.com>
*
*/


/* -------------------------------------------------------------------------- */

#include "nu_rt_prog_ctx.h"
#include "nu_stmt_call.h"
#include "nu_error_codes.h"
#include "nu_functions.h"
#include "nu_expr_function.h"
#include "nu_expr_var.h"

#include <string>


/* -------------------------------------------------------------------------- */

namespace nu
{


/* -------------------------------------------------------------------------- */

void stmt_call_t::run(rt_prog_ctx_t& ctx)
{
   run(ctx, ctx.runtime_pc.get_line());
}


/* -------------------------------------------------------------------------- */

void stmt_call_t::run(
   rt_prog_ctx_t& ctx,
   const prog_pointer_t::line_number_t& line)
{

   auto rt_error_if = [&](
                         bool cond,
                         rt_error_code_t::value_t err,
                         const std::string& desc)
   {
      rt_error_code_t::get_instance().throw_if(
         cond,
         ctx.runtime_pc.get_line(),
         err,
         desc);
   };

   auto i = ctx.proc_prototypes.data.find(_name);

   bool undef = false;

   // Verify if "CALL" statement is applied to built-in function
   // (however its return value will be ignored, but function will be executed
   //  like a procedure)
   if (i == ctx.proc_prototypes.data.end())
   {
      auto & gf = functions_t::get_instance();
      undef = !gf.is_defined(_name);

      if (undef == false)
      {
         func_args_t function_args;

         for (const auto & arg : _args)
            function_args.push_back(arg.first);

         expr_any_t::handle_t fhandle(
            std::make_shared<expr_function_t>(
               _name, function_args));

         assert(fhandle);

         fhandle->eval(ctx);

         ctx.go_to_next();

         return;
      }
   }

   rt_error_if(
      undef,
      rt_error_code_t::E_FUNC_UNDEF,
      _name);

   auto & prototype = i->second.second;

   rt_error_if(
      prototype.parameters.size() != _args.size(),
      rt_error_code_t::E_WRG_NUM_ARGS,
      _name);

   auto pit = prototype.parameters.begin();

   //Evaluate sub arguments
   std::list<variant_t> values;

   for (auto arg : _args)
   {
      auto scope =
         ctx.proc_scope.get(proc_scope_t::type_t::LOCAL);

      variant_t val;

      if (arg.first == nullptr)
         val = variant_t("");

      else
         val = arg.first->eval(ctx);

#if 0 //TODO
      // Check array type mismatch
      rt_error_if(
         pit == prototype.parameters.end() ||
         (! pit->array_type && val.is_vector()),
         rt_error_code_t::E_TYPE_ILLEGAL,
         "'" + pit->var_name + "'");
#endif

      if (pit->reference)
      {
         auto var_ptr = dynamic_cast<expr_var_t*> (arg.first.get());

         rt_error_if(
            !var_ptr,
            rt_error_code_t::E_TYPE_ILLEGAL,
            "'" + pit->var_name + "'");

         if (!scope->is_defined(var_ptr->name()))
         {
            auto glb_scope =
               ctx.proc_scope.get(proc_scope_t::type_t::GLOBAL);

            rt_error_if(
               !glb_scope->is_defined(var_ptr->name()),
               rt_error_code_t::E_VAR_UNDEF,
               "'" + var_ptr->name() + "'");

            scope = glb_scope;
         }

         const auto & var = variant_t(&((*scope)[var_ptr->name()]));

         rt_error_if(
            val.get_type() != var.get_type(),
            rt_error_code_t::E_TYPE_MISMATCH,
            "'" + pit->var_name + "'");

         values.push_back(var);
      }

      else
      {
         values.push_back(val);
      }

      ++pit;
   }


   // Enter calling-subroutine scope
   ctx.set_return_line(std::make_pair(line, get_stmt_id()));
   ctx.go_to(i->second.first);
   ctx.proc_scope.enter_scope(_name, _fncall);

   auto arg_it = prototype.parameters.begin();
   auto sub_xscope = ctx.proc_scope.get();

   auto ret_type =
      variable_t::type_by_typename(i->second.second.ret_type);

   std::string initvalue =
      ret_type != variable_t::type_t::STRING ? "0" : "";

   // Define the local variable used to return ret-value
   sub_xscope->define(_name, nu::variant_t(initvalue, ret_type));

   if (!values.empty())
   {
      auto values_it = values.begin();

      for (auto arg : _args)
      {
         const auto & val = *values_it;

         const auto variable_name = arg_it->var_name;
         int vsize = 0;

         if (arg_it->vsize)
         {
            auto size = arg_it->vsize->eval(ctx);
            vsize = size.to_int();
         }

         auto var_type = variable_t::type_by_typename(arg_it->type_name);

#if 0 //TODO
         rt_error_if(
            vsize && (!val.is_vector() || int(val.vector_size()) != vsize),
            rt_error_code_t::E_TYPE_MISMATCH,
            _name + ": '" + variable_name + "' array ");
#endif

         switch (val.get_type())
         {
            case variant_t::type_t::INTEGER:
            case variant_t::type_t::FLOAT:
               rt_error_if(
                  var_type != variable_t::type_t::FLOAT &&
                  var_type != variable_t::type_t::INTEGER,
                  rt_error_code_t::E_TYPE_MISMATCH,
                  _name + ": Parameter '" + variable_name + "'");
               break;

            case variant_t::type_t::BYTE:
               rt_error_if(
                  var_type != variable_t::type_t::BYTE,
                  rt_error_code_t::E_TYPE_MISMATCH,
                  _name + ", Parameter '" + variable_name + "'");
               break;

            case variant_t::type_t::STRING:
            default:
               if (val.get_type() == variant_t::type_t::UNDEFINED)
               {
                  rt_error_if(
                     true,
                     rt_error_code_t::E_TYPE_ILLEGAL,
                     _name + ", Parameter '" + variable_name + "'");
               }

               else
               {
                  rt_error_if(
                     var_type != variable_t::type_t::STRING,
                     rt_error_code_t::E_TYPE_MISMATCH,
                     _name + ", Parameter '" + variable_name + "'");
               }

               break;
         } //switch

         sub_xscope->define(variable_name, val);

         ++arg_it;
         ++values_it;

      } //for
   }

}


/* -------------------------------------------------------------------------- */

} // namespace nu


/* -------------------------------------------------------------------------- */

